#include "camera.h"
#include "..\math\vector.h"
#include "..\math\matrix.h"
#include "..\math\math.h"
#include "..\math\quaternion.h"

#include "..\render\d3d.h"
#include "..\render\d3d_util.h"
#include "..\input\dinput.h"
#include "..\common\def.h"
//#include "memorymanager.h"
#include "engine.h"

#include "..\math\bezier.h"
CCamera::CCamera(void) : m_followDistance(10.0F), CObject()
{
	EE_ZEROMEMORY(&m_Data, sizeof(CCamera::cameraData)); 
	

	m_yaw	= 0.0f; 
	m_pitch	= 0.0f; 
	m_mouseVelocity = 0.0f; 

	m_nearPlane	= 10.0f; 
	m_farPlane	= 150.0f; 
	m_fov		= DEGTORAD(90.0f); 
	cameraForward	= Vector3f(0.0f,0.0f,1.0f); 

	m_BezierFrac = 0.0f; 
	m_pPositionBezier = NULL; 
	m_pDirectionBezier = NULL; 
	pUpdateFunc = NULL; 

	m_State = CCamera::STATIC; 
}

CCamera::~CCamera(void)
{ }

HRESULT CCamera::initalizeProjection(const float nearPlane, const float farPlane, const float fov)
{
	m_farPlane	= MAX(farPlane,nearPlane); 
	m_nearPlane	= MIN(farPlane,nearPlane); 
	m_fov = CLAMP(fov, PI, 0.0f); 

	CD3D9Device *pRenderer = CEngine::GetInstance()->GetRenderer(); 
	if(pRenderer == NULL)
		return E_FAIL; 


	DeviceSettings ds = CEngine::GetInstance()->GetRenderer()->GetDeviceSettings();
	pRenderer->GetViewOrrientation()->m_Projection = GetProjectionTransform(m_nearPlane,m_farPlane,m_fov,((float)ds.screenWidth ) / ((float)ds.screenHeight));
	return S_OK; 
}

HRESULT CCamera::initalizeCamera(const CCamera::cameraData& data)
{
	
	m_Data.m_mouseAccelerate = MAX(0.0f,data.m_mouseAccelerate); 
	m_Data.m_mouseSpeed = MAX(0.0f,data.m_mouseSpeed); 
	m_Data.m_walkSpeed = MAX(0.0f,data.m_walkSpeed); 

	m_yaw = 0.0f; 
	m_pitch = 0.0f; 
	m_mouseVelocity = 0.0f; 
	cameraForward = Vector3f(0.0f,0.0f,1.0f); 

	return initalizeProjection(m_nearPlane,m_farPlane, m_fov); 
}

HRESULT CCamera::releaseCamera()
{ 
	EE_ZEROMEMORY(&m_Data, sizeof(CCamera::cameraData)); 

	pUpdateFunc = NULL; 
	m_State = CCamera::STATIC; 	
	return S_OK; 
}

void CCamera::setDirection(const Vector3f& direction)
{	
	if(Vector3f::isZero(&direction) == false)
	{
		const bool isVertical = (direction.x == 0.0f && direction.z == 0.0f); 
		cameraForward = Vector3f::normalOf(isVertical ? &Vector3f(0.1f,10.0f,0.1f) : &direction); 

		Vector3f cameraRight; 
		Vector3f cameraUp; 
		Vector3f cameraTranslation = m_transform.Translation(); 

		Vector3f::normalize(&cameraForward); 
		Vector3f::cross(&cameraRight, &Vector3f::UP, &cameraForward); 
		Vector3f::normalize(&cameraRight); 
		Vector3f::cross(&cameraUp, &cameraForward, &cameraRight); 
		Vector3f::normalize(&cameraUp); 

		m_transform = Matrix4f(	cameraRight.x, cameraUp.x, cameraForward.x, 0.0f,
					cameraRight.y, cameraUp.y, cameraForward.y, 0.0f,
					cameraRight.z, cameraUp.z, cameraForward.z, 0.0f,
					cameraTranslation.x,
					cameraTranslation.y, 
					cameraTranslation.z, 1.0f); //); 
	}
}

Vector3f CCamera::getPosition()
{
	return m_transform.Translation();
}

Vector3f CCamera::getDirection()
{	
	return m_transform.Foward();
}

void CCamera::UpdateFollowBeizer(const float deltaTime)
{
	if(ISNULL(m_pPositionBezier))
	{
		setState(STATIC); 
	}
	else
	{
		unsigned int posPointCount = m_pPositionBezier->GetPointCount(); 

		if(posPointCount > 0)
		{
			Vector3f position; 
			Vector3f direction; 

			float fPosFrac = m_BezierFrac / posPointCount; 

			//If we have a direction path to follow
			if(m_pDirectionBezier)
			{
				unsigned int dirPointCount = m_pDirectionBezier->GetPointCount(); 
				if(dirPointCount > 0)
				{
					float fDirFrac = m_BezierFrac / dirPointCount; 
					m_pDirectionBezier->Evaluate(&direction, fDirFrac); 
				}
			}
			else
			{
			
				m_pPositionBezier->Evaluate(&direction, fPosFrac + 0.0125f); 
			}
				if(fPosFrac + 0.0125f > 1.0f)
				{
					m_BezierFrac = 0.0f; 
					fPosFrac = 0.0f; 
				}
				else
				{
			m_BezierFrac += deltaTime; 
				}
		
			m_pPositionBezier->Evaluate(&position, fPosFrac); 

			Vector3f forward = direction - position; 
			Vector3f::normalize(&forward); 
			Vector3f right; 
			Vector3f::crossNormalize(&right, &forward, &Vector3f::UP); 

			Vector3f up; 
			Vector3f::crossNormalize(&up, &right, &forward); 



			m_transform.SetTranslation(position); 
			m_transform.SetForward(forward); 
			m_transform.SetRight(-right); 
			m_transform.SetUp(-up); 
		}		
	}
}	

//TODO: remove/redundant
void CCamera::UpdateFreeView(const float deltaTime)
{
	CDInput9Device* pInput = CEngine::GetInstance()->GetInput(); 
	if(ISNULL(pInput))
		return; 

	const char* pCharMap = pInput->getKeyMap(); 
	Vector2i mouseDelta = pInput->getMouseDelta(); 

	float walkMag = 0.0f; 
	float strafeMag = 0.0f; 

	if(pCharMap[DIK_S])
		walkMag -= m_Data.m_walkSpeed * (pCharMap[DIK_LSHIFT] ? 1 : .25); 
	if(pCharMap[DIK_W])
		walkMag += m_Data.m_walkSpeed * (pCharMap[DIK_LSHIFT] ? 1 : .25); 
	if(pCharMap[DIK_D])
		strafeMag += m_Data.m_walkSpeed * (pCharMap[DIK_LSHIFT] ? 1 : .25); 
	if(pCharMap[DIK_A])
		strafeMag -= m_Data.m_walkSpeed * (pCharMap[DIK_LSHIFT] ? 1 : .25); 

	const float mouseVelocity = mouseDelta.length() * m_Data.m_mouseSpeed; 
	//m_mouseVelocity = ((mouseVelocity * deltaTime) + (m_Data.m_mouseAccelerate * (mouseVelocity - m_mouseVelocity) * deltaTime)) * deltaTime; 
	if(mouseVelocity)
		m_mouseVelocity = mouseVelocity/mouseVelocity; 
	else
m_mouseVelocity =0;
	if(walkMag != 0.0f || strafeMag != 0.0f || m_mouseVelocity != 0.0f)
	{
		Vector3f cameraRight; 
		Vector3f cameraUp; 

		Vector3f::normalize(&cameraForward); 
		Vector3f::cross(&cameraRight, &Vector3f::UP, &cameraForward); 
		Vector3f::normalize(&cameraRight); 
		Vector3f::cross(&cameraUp, &cameraForward, &cameraRight); 
		Vector3f::normalize(&cameraUp); 

		QUATERNION::RotateVector(DEGTORAD(mouseDelta.x) * m_mouseVelocity, Vector3f::UP, &cameraForward); 

		Vector3f tempForward = cameraForward; 
		QUATERNION::RotateVector(DEGTORAD(mouseDelta.y) * m_mouseVelocity, cameraRight, &tempForward); 

		if(Vector3f::angle(&tempForward, &Vector3f(cameraForward.x,0,cameraForward.z)) < DEGTORAD(85.0f))
		{
			cameraForward = tempForward; 
			Vector3f::normalize(&cameraForward); 
		}

		const Vector3f displacement = ((cameraForward * walkMag) + (cameraRight * strafeMag)) * deltaTime; 

		m_transform._M11 = cameraRight.x; 
		m_transform._M21 = cameraRight.y; 
		m_transform._M31 = cameraRight.z; 

		m_transform._M12 = cameraUp.x; 
		m_transform._M22 = cameraUp.y; 
		m_transform._M32 = cameraUp.z; 

		m_transform._M13 = cameraForward.x; 
		m_transform._M23 = cameraForward.y; 
		m_transform._M33 = cameraForward.z; 

		m_transform._M41 = m_transform._M41 + displacement.x; 
		m_transform._M42 = m_transform._M42 + displacement.y; 
		m_transform._M43 = m_transform._M43 + displacement.z; 
	}
}

void CCamera::ApplyFreeview(CCamera* pCamera, const Vector3f& deltaMove, const float deltaYaw, const float deltaPitch)
{
	if(ISNULL(pCamera))
		return; 

	CEngine::PrintVector(pCamera->m_transform.Translation());
	if(Vector3f::dot(&deltaMove,&deltaMove) > 0.0f || deltaPitch != 0.0f || deltaYaw != 0.0f)
	{
		
		Matrix4f *pTransform = &pCamera->m_transform;

		Vector3f cameraForward = pCamera->cameraForward;
		Vector3f cameraRight; 
		Vector3f cameraUp; 

		Vector3f::normalize(&cameraForward); 
		Vector3f::cross(&cameraRight, &Vector3f::UP, &cameraForward); 
		Vector3f::normalize(&cameraRight); 
		Vector3f::cross(&cameraUp, &cameraForward, &cameraRight); 
		Vector3f::normalize(&cameraUp); 

		QUATERNION::RotateVector(DEGTORAD(deltaYaw), Vector3f::UP, &cameraForward); 

		Vector3f tempForward = cameraForward; 
		QUATERNION::RotateVector(DEGTORAD(deltaPitch), cameraRight, &tempForward); 

		if(Vector3f::angle(&tempForward, &Vector3f(cameraForward.x,0,cameraForward.z)) < DEGTORAD(85.0f))
		{
			cameraForward = tempForward; 
			Vector3f::normalize(&cameraForward); 

		}

		pCamera->cameraForward = cameraForward;

		const Vector3f displacement = (cameraForward * deltaMove.z) + (cameraRight * -deltaMove.x) + Vector3f(0.0f, deltaMove.y, 0.0f);


		pTransform->_M11 = cameraRight.x; 
		pTransform->_M21 = cameraRight.y; 
		pTransform->_M31 = cameraRight.z; 

		pTransform->_M12 = cameraUp.x; 
		pTransform->_M22 = cameraUp.y; 
		pTransform->_M32 = cameraUp.z; 

		pTransform->_M13 = cameraForward.x; 
		pTransform->_M23 = cameraForward.y; 
		pTransform->_M33 = cameraForward.z; 

		pTransform->_M41 = pTransform->_M41 + displacement.x; 
		pTransform->_M42 = pTransform->_M42 + displacement.y; 
		pTransform->_M43 = pTransform->_M43 + displacement.z; 
	}
}
void CCamera::Update(const float deltaTime)
{
	switch(m_State)
	{
	case CCamera::FREEVIEW:
		UpdateFreeView(deltaTime); 
		break; 
	case CCamera::FOLLOW_BEIZER:
		UpdateFollowBeizer(deltaTime); 
		break; 
	case CCamera::STATIC:default:
		break; 
	}

	if(pUpdateFunc && m_State != CCamera::FOLLOW_BEIZER)
		pUpdateFunc(&m_transform); 

	const Vector3f translation(m_transform._M41,m_transform._M42,m_transform._M43); 
	const Vector3f right(m_transform._M11,m_transform._M21,m_transform._M31); 
	const Vector3f up(m_transform._M12,m_transform._M22,m_transform._M32); 
	const Vector3f forward(m_transform._M13,m_transform._M23,m_transform._M33); 

	D3DXVECTOR3 pos(m_transform._M41,m_transform._M42,m_transform._M43);
	D3DXVECTOR3 lookat(m_transform._M13 + m_transform._M41,m_transform._M23 + m_transform._M42,m_transform._M33 + m_transform._M43);

	ViewOrrientation* pView = CEngine::GetInstance()->GetRenderer()->GetViewOrrientation();
	D3DXMatrixLookAtLH(&pView->m_View, &pos, &lookat, &D3DXVECTOR3(0,1,0));
	pView->m_Position = pos;
}

void CCamera::setUpdateFunc(void (*pFunc) (Matrix4f*))
{
	pUpdateFunc = pFunc; 
}


void CCamera::setPosition(const Vector3f &position)
{	
	m_transform.SetTranslation(position);
}

void CCamera::setDirection(CObject *pEntity)
{
	if(ISNULL(pEntity))
		return;

	Vector3f direction = pEntity->getPosition() - m_transform.Translation();
	Vector3f::normalize(&direction);

	setDirection(direction);
}

float CCamera::getFarPlane(void)
{
	return m_farPlane; 
}
float CCamera::getNearPlane(void)
{
	return m_nearPlane; 
}

CCamera::CameraState CCamera::getState(void)
{
	return m_State; 
}

void CCamera::setBeizerPaths(Bezier* pPositionPath,Bezier* pLookatPath)
{
	if(pPositionPath)
	{
		m_pDirectionBezier = pLookatPath; 
		m_pPositionBezier = pPositionPath; 
		m_State = FOLLOW_BEIZER; 
	}
	else
	{
		m_pDirectionBezier = NULL; 
		m_pPositionBezier = NULL; 
		m_State = STATIC; 
	}
}

void CCamera::setState(CameraState state)
{
	m_State = state; 
}
